////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Michiel Helvensteijn                                    //
//                                                                            //
// This file is part of the Mist compiler.                                    //
//                                                                            //
// The Mist compiler is free software: you can redistribute it and/or modify  //
// it under the terms of the GNU General Public License as published by       //
// the Free Software Foundation, either version 3 of the License, or          //
// (at your option) any later version.                                        //
//                                                                            //
// The Mist compiler is distributed in the hope that it will be useful,       //
// but WITHOUT ANY WARRANTY; without even the implied warranty of             //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              //
// GNU General Public License for more details.                               //
//                                                                            //
// You should have received a copy of the GNU General Public License          //
// along with the Mist compiler.  If not, see <http://www.gnu.org/licenses/>. //
////////////////////////////////////////////////////////////////////////////////

#include "visitors/astvisitor.hpp"

/******************************************************************************
 * Includes                                                                   *
 ******************************************************************************/

#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

#include <sstream>
using std::ostringstream;

#include <climits>

#include <typeinfo>

#include "ast/nodetypes.hpp"
#include "visitors/visitor.hpp"
#include "visitors/echovisitor.hpp"
#include "codestream.hpp"
#include "assert.hpp"

/******************************************************************************
 * AstVisitor class                                                           *
 ******************************************************************************/

class AstVisitor : public Visitor {
	public:
		
		AstVisitor(ostream& stream);
		
		~AstVisitor();
		
		void visit(shared_ptr<ArithmeticOperation>   node);
		void visit(shared_ptr<FunctionCall>          node);
		void visit(shared_ptr<UnaryOperation>        node);
		void visit(shared_ptr<Identifier>            node);
		void visit(shared_ptr<IntegerLiteral>        node);
		void visit(shared_ptr<BooleanLiteral>        node);
		void visit(shared_ptr<Null>                  node);
		void visit(shared_ptr<ArrayType>             node);
		void visit(shared_ptr<IntegerType>           node);
		void visit(shared_ptr<BooleanType>           node);
		void visit(shared_ptr<Assertion>             node);
		void visit(shared_ptr<IfStatement>           node);
		void visit(shared_ptr<WhileStatement>        node);
		void visit(shared_ptr<Assignment>            node);
		void visit(shared_ptr<IdAssignment>          node);
		void visit(shared_ptr<CompoundStatement>     node);
		void visit(shared_ptr<FunctionType>          node);
		void visit(shared_ptr<VoidType>              node);
		void visit(shared_ptr<VariableDeclaration>   node);
		void visit(shared_ptr<FunctionDeclaration>   node);
		void visit(shared_ptr<Declarations>          node);
		void visit(shared_ptr<Subscripting>          node);
		void visit(shared_ptr<ComparisonChain>       node);
		void visit(shared_ptr<TypeError>             node);
		void visit(shared_ptr<AndChain>              node);
		void visit(shared_ptr<OrChain>               node);
		void visit(shared_ptr<ImpliesChain>          node);
		void visit(shared_ptr<ForStatement>          node);
		void visit(shared_ptr<Range>                 node);
		void visit(shared_ptr<RangeType>             node);
		void visit(shared_ptr<Reference>             node);
		void visit(shared_ptr<BooleanPointerLiteral> node);
		void visit(shared_ptr<IntegerPointerLiteral> node);
	
	private:
		
		CodeStream& cdout;
};

/******************************************************************************
 * Function Implementations                                                   *
 ******************************************************************************/

ast::ast(shared_ptr<Node> tree) : _tree(tree) {
	assert(tree);
}

ast::operator string() const {
	ostringstream sout;
	
	sout << *this;
	
	return sout.str();
}

ostream& ast::send_to_stream(ostream& out) const {
	shared_ptr<AstVisitor> visitor(new AstVisitor(out));
	
	_tree->accept(visitor);
	
	return out;
}

/******************************************************************************/

AstVisitor::AstVisitor(ostream& out) : cdout(*new CodeStream(out, 2)) {
} // intentionally empty

AstVisitor::~AstVisitor() {
	delete &cdout;
}

void AstVisitor::visit(shared_ptr<ArithmeticOperation> node) {
	assert(node);
	
	cdout << "ArithmeticOperation\n" << indent;
	
	cdout << "type: " << to_string(node->operator_type()) << "\n";
	
	cdout << "left operand: ";
	node->left_operand()->accept(shared_from_this());
	
	cdout << "right operand: ";
	node->right_operand()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<FunctionCall> node) {
	assert(node);
	
	cdout << "FunctionCall\n" << indent;
	
	cdout << "base: ";
	node->base()->accept(shared_from_this());
	
	cdout << "parameters:\n" << indent;
	foreach (shared_ptr<Expression> parameter, node->parameters()) {
		cdout << "* ";
		parameter->accept(shared_from_this());
	}
	cdout << unindent;
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<UnaryOperation> node) {
	assert(node);
	
	cdout << "UnaryOperation\n" << indent
	      << "type: " << to_string(node->operator_type()) << '\n'
	      << "operand: ";
	
	node->operand()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<Identifier> node) {
	assert(node);
	
	cdout << "Identifier\n" << indent
	      << "text: " << node->text() << '\n'
	      << unindent;
}

void AstVisitor::visit(shared_ptr<IntegerLiteral> node) {
	assert(node);
	
	cdout << "IntegerLiteral\n" << indent
	      << "value: " << node->value() << '\n'
	      << unindent;
}

void AstVisitor::visit(shared_ptr<BooleanLiteral> node) {
	assert(node);
	
	cdout << "BooleanLiteral\n" << indent
	      << "value: " << (node->value() ? "true" : "false") << '\n'
	      << unindent;
}

void AstVisitor::visit(shared_ptr<Null> node) {
	assert(node);
	
	cdout << "Null\n";
}

void AstVisitor::visit(shared_ptr<ArrayType> node) {
	assert(node);
	
	cdout << "ArrayType\n" << indent;
	
	cdout << "pointer-type: "
	      << (node->weak_pointer() ? "weak" : "strong")
	      << "\n";
	
	cdout << "size: ";
	node->size()->accept(shared_from_this());
	
	cdout << "content-type: ";
	node->content_type()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<IntegerType> node) {
	assert(node);
	
	cdout << "IntegerType" << indent;
	
	cdout << "pointer-type: "
	      << (node->weak_pointer() ? "weak" : "strong")
	      << "\n";
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<BooleanType> node) {
	assert(node);
	
	cdout << "BooleanType" << indent;
	
	cdout << "pointer-type: "
	      << (node->weak_pointer() ? "weak" : "strong")
	      << "\n";
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<Assertion> node) {
	assert(node);
	
	cdout << "Assertion\n" << indent;
	
	if (node->condition_is_set()) {
		cdout << "condition: ";
		node->condition()->accept(shared_from_this());
	} else {
		cdout << "declaration: ";
		node->variable_declaration()->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<IfStatement> node) {
	assert(node);
	
	cdout << "IfStatement\n" << indent;
	
	cdout << "condition: ";
	node->condition()->accept(shared_from_this());
	
	cdout << "then-statement: ";
	node->then_statement()->accept(shared_from_this());
	
	if (node->else_statement_is_set()) {
		cdout << "else-statement: ";
		node->else_statement()->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<WhileStatement> node) {
	assert(node);
	
	cdout << "WhileStatement\n" << indent;
	
	cdout << "condition: ";
	node->condition()->accept(shared_from_this());
	
	cdout << "statement: ";
	node->statement()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<Assignment> node) {
	assert(node);
	
	cdout << "Assignment\n" << indent;
	
	cdout << "left-side: ";
	node->left_side()->accept(shared_from_this());
	
	cdout << "right-side: ";
	node->right_side()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<IdAssignment> node) {
	assert(node);
	
	cdout << "IdAssignment\n" << indent;
	
	cdout << "left-side: ";
	node->left_side()->accept(shared_from_this());
	
	cdout << "right-side: ";
	node->right_side()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<CompoundStatement> node) {
	assert(node);
	
	cdout << "CompoundStatement\n" << indent;
	
	if (node->name_is_set()) {
		cdout << "name: ";
		node->name()->accept(shared_from_this());
	}
	
	foreach (shared_ptr<Statement> statement, node->statements()) {
		cdout << "* ";
		statement->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<FunctionType> node) {
	assert(node);
	
	cdout << "FunctionType\n" << indent;
	
	cdout << "pointer-type: "
	      << (node->weak_pointer() ? "weak" : "strong")
	      << "\n";
	
	cdout << "return-type: ";
	
	node->return_type()->accept(shared_from_this());
	
	cdout << "parameter-types:\n" << indent;
	foreach (ParameterType parameter, node->parameter_types()) {
		cdout << "* " << parameter.policy->name;
		parameter.type->accept(shared_from_this());
	}
	cdout << unindent;
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<VoidType> node) {
	assert(node);
	
	cdout << "VoidType\n";
}

void AstVisitor::visit(shared_ptr<VariableDeclaration> node) {
	assert(node);
	
	cdout << "VariableDeclaration\n" << indent;
	
	cdout << "type: ";
	node->type()->accept(shared_from_this());
	
	cdout << "variables:\n" << indent;
	foreach (VarDec var, node->variables()) {
		cdout << "* variable:\n";
		cdout << indent << "name: ";
		var.name->accept(shared_from_this());
		if (var.is_initialized()) {
			cdout << "initialization: ";
			if (var.init_is_identity)
				cdout << "<-- ";
			else
				cdout << "<- ";
			var.init->accept(shared_from_this());
		}
		cdout << unindent;
	}
	cdout << unindent;
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<FunctionDeclaration> node) {
	assert(node);
	
	cdout << "FunctionDeclaration\n" << indent;
	
	cdout << "name: ";
	node->name()->accept(shared_from_this());
	
	cdout << "type: ";
	node->type()->accept(shared_from_this());
	
	cdout << "parameter-names:\n" << indent;
	foreach (shared_ptr<Identifier> name, node->parameter_names()) {
		cdout << "* ";
		name->accept(shared_from_this());
	}
	cdout << unindent;
	
	cdout << "precondition:\n" << indent;
	foreach (shared_ptr<Assertion> assertion, node->preconditions()) {
		cdout << "* ";
		assertion->accept(shared_from_this());
	}
	cdout << unindent;
	
	cdout << "postcondition:\n" << indent;
	foreach (shared_ptr<Assertion> assertion, node->postconditions()) {
		cdout << "* ";
		assertion->accept(shared_from_this());
	}
	cdout << unindent;
	
	cdout << "statement: ";
	node->statement()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<Declarations> node) {
	assert(node);
	
	cdout << "Declarations\n" << indent;
	
	foreach (shared_ptr<Declaration> declaration, node->declarations()) {
		cdout << "* ";
		declaration->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<Subscripting> node) {
	assert(node);
	
	cdout << "Subscripting\n" << indent;
	
	cdout << "base: ";
	node->base()->accept(shared_from_this());
	
	cdout << "subscript: ";
	node->subscript()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<ComparisonChain> node) {
	assert(node);
	
	cdout << "ComparisonChain\n" << indent;
	
	for (unsigned int i = 0; i < node->comparison_operator_count(); ++i) {
		cdout << "* ";
		node->operands()[i]->accept(shared_from_this());
		
		cdout << "* " << to_string(node->comparison_operators()[i]) << "\n";
	}
	cdout << "* ";
	node->operands()[node->comparison_operator_count()]->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<TypeError> node) {
	assert(node);
	
	cdout << "TypeError\n";
}

void AstVisitor::visit(shared_ptr<AndChain> node) {
	assert(node);
	
	cdout << "AndChain\n" << indent;
	
	foreach (shared_ptr<Expression> operand, node->operands()) {
		cdout << "* ";
		operand->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<OrChain> node) {
	assert(node);
	
	cdout << "OrChain\n" << indent;
	
	foreach (shared_ptr<Expression> operand, node->operands()) {
		cdout << "* ";
		operand->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<ImpliesChain> node) {
	assert(node);
	
	cdout << "ImpliesChain\n" << indent;
	
	foreach (shared_ptr<Expression> operand, node->operands()) {
		cdout << "* ";
		operand->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<ForStatement> node) {
	assert(node);
	
	cdout << "ForStatement\n" << indent;
	
	cdout << "iterator: ";
	node->iterator()->accept(shared_from_this());
	
	cdout << "range: ";
	node->range()->accept(shared_from_this());
	
	cdout << "statement: ";
	node->statement()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<Range> node) {
	assert(node);
	
	cdout << "Range\n" << indent;
	
	cdout << "lower-bound: ";
	node->lower_bound()->accept(shared_from_this());
	
	cdout << "upper-bound: ";
	node->upper_bound()->accept(shared_from_this());
	
	if (!node->step()->equals(IntegerLiteral(1))) {
		cdout << "step-size: ";
		node->step()->accept(shared_from_this());
	}
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<RangeType> node) {
	assert(node);
	
	cdout << "RangeType\n" << indent;
	
	cdout << "pointer-type: "
	      << (node->weak_pointer() ? "weak" : "strong")
	      << "\n";
	
	cdout << "type: ";
	node->type()->accept(shared_from_this());
	
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<Reference> node) {
	assert(node);
	
	cdout << "Reference\n" << indent
	      << "path: " << node->path() << '\n'
	      << unindent;
}

void AstVisitor::visit(shared_ptr<BooleanPointerLiteral> node) {
	assert(node);
	
	cdout << "BooleanPointerLiteral\n" << indent
	      << "value: ";
	node->value()->accept(shared_from_this());
	cdout << unindent;
}

void AstVisitor::visit(shared_ptr<IntegerPointerLiteral> node) {
	assert(node);
	
	cdout << "IntegerPointerLiteral\n" << indent
	      << "value: ";
	node->value()->accept(shared_from_this());
	cdout << unindent;
}
